/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package recommender.controller;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import recommender.bayesian.BayesianBuilder;
import recommender.bayesian.BayesianReader;
import recommender.dbi.DBI;
import recommender.dbi.KBI;
import recommender.dbi.LTUBI;
import recommender.dbi.LTVI;
import recommender.dbi.UBI;
import recommender.dbi.VBI;
import recommender.objects.Context;
import recommender.objects.Entity;
import recommender.objects.Parent;
import recommender.objects.Preference;
import recommender.objects.User;
import recommender.objects.Video;

/**
 *
 * @author Aya
 */
public class PersonalRecommender {

    private static String kbFile = "Knowledge Base.xdsl";
    private static String vbFile = "Video Base.xdsl";
    private static String path = "C:\\Users\\Aya\\RWTH Aachen\\Courses\\Thesis\\Thesis\\Created Networks";
    public static final DBI kbi = new KBI();
    public static final VBI vbi = new LTVI();
    public static final UBI ubi = new LTUBI();
    private static Map<Integer, User> umap = new HashMap<>();

    public static void setKBFile(String filename) {
        kbFile = filename;
    }

    public static void setVBFile(String filename) {
        vbFile = filename;
    }

    public static void setPath(String p) {
        path = p;
    }

    public static User login(String uname, String pwd) {
        User u = ubi.loadUser(uname, pwd);
        if (u != null) {
            umap.put(u.getId(), u);
        }
        return u;
    }

    public static void logout(int id) {
        umap.remove(id);
        System.out.println("Currently logged in: ");
        for (Entry<Integer, User> e : umap.entrySet()) {
            System.out.println("User " + e.getKey());
        }
    }
    
    public static Map<Video, Double> processVideoOutput(Map<String, Double> nodes){
        List<Map.Entry<String, Double>> sorted = sortByValues(nodes);
        // Map recommended nodes to Videos
        DecimalFormat df = new DecimalFormat("#.#");
        Map<Video, Double> recommendations = new LinkedHashMap<>();
        for (Entry<String, Double> sorted1 : sorted) {
            try {
                Video v = vbi.getVideo(sorted1.getKey());
                recommendations.put(v, Double.valueOf(df.format(100 * sorted1.getValue())));
            } catch (Exception e) {
                System.out.println("Video " + sorted1.getKey() + " not found in the database");
                e.printStackTrace();
            }
        }
        return recommendations;
    }
    
    public static Map<Video, Double> fetchRecommendations(List<Integer> group){
        String bnfile;
        if(group.size() > 3){
            return null;
        } else {
            switch(group.size()){
                case 2: bnfile = "Group2.xdsl"; break;
                case 3: bnfile = "Group3.xdsl"; break;
                default: bnfile = "Group2.xdsl"; break;    
            }
            BayesianReader kbr = new BayesianReader(path, bnfile, 0);
            int i=1;
            for(int uid: group){
                User u = umap.get(uid);
                if (u != null) {
                    kbr.setGroupEvidence(i, u.getLikeList(), u.getDislikeList(), u.getExplicitInterests(), u.getExplicitDislikes());
                    i++;        
                }
            }
            Map<String, Double> nodes = kbr.getGroupRecommendations(20);
            return processVideoOutput(nodes);
        }
    }

    public static Map<Video, Double> fetchRecommendations(int uid) {
        // Set the evidences in the network
        BayesianReader kbr = new BayesianReader(path, vbFile, 0);
        User u = umap.get(uid);
        if (u != null) {
            kbr.setEvidence(u.getLikeList(), u.getDislikeList(), u.getExplicitInterests(), u.getExplicitDislikes());
            // Read recommendations
            Map<String, Double> nodes;
            if(u.getPreferences().isThreshold()){
                nodes = kbr.getRecommendationsByThreshold(u.getPreferences().getThresholdV());
            } else {
                nodes = kbr.getRecommendations(u.getPreferences().getVideoN());
            }
            return processVideoOutput(nodes);
        } else {
            return new HashMap<>();
        }
    }

    public static Map<Entity, Double> fetchInferredInterests(int uid) {
        // Set the evidences in the network
        BayesianReader kbr = new BayesianReader(path, vbFile, 0);
        User u = umap.get(uid);
        if(u != null){
        kbr.setEvidence(u.getLikeList(), u.getDislikeList(), u.getExplicitInterests(), u.getExplicitDislikes());
        // Read Interests
        Map<String, Double> nodes;
        if(u.getPreferences().isThreshold()){
            nodes = kbr.getImplicitInterestsByThreshold(u.getPreferences().getThresholdI());
        } else {
            nodes = kbr.getImplicitInterests(u.getPreferences().getInterestN());
        }
        List<Map.Entry<String, Double>> sorted = sortByValues(nodes);
        // Map interesting nodes to entities
        DecimalFormat df = new DecimalFormat("#.###");
        Map<Entity, Double> interests = new LinkedHashMap<>();
        for (Entry<String, Double> sorted1 : sorted) {
            String node = sorted1.getKey();
            try {
                Entity e = kbi.getEntity(node);
                interests.put(e, Double.valueOf(df.format(100 * sorted1.getValue())));
            } catch (Exception e) {
                System.out.println("Entity " + node + " not found in the database");
            }
        }
        return interests;
        }
        else {
            return new HashMap<>();
        }
    }
    
    private static boolean in(Parent p, List<Entity> entities){
        for(Entity e: entities){
            if(p.getId() == e.getId() && p.isClass() == e.isClass())
                return true;
        }
        return false;
    }
    
    public static void buildKB(String name, List<Entity> entities) {
        BayesianBuilder kbb = new BayesianBuilder(path, name);
        for (Entity entity : entities) {
            kbb.createNode(entity);
        }
        // Create connections between the nodes
        try {
            for (Entity entity : entities) {
                List<Parent> parents = kbi.getParentsOf(entity);
                List<Parent> valid_parents = new ArrayList<>();
                for(Parent p: parents){
                    if(in(p, entities)){
                        valid_parents.add(p);
                    } else {
                        System.out.println("Detected a problem");
                    }
                }
                kbb.createArc(entity, valid_parents);
            }
        } catch (Exception ex) {
            System.out.println("Detected a flaw in the knowledge data, please review. \n Details: " + ex.getMessage());
            Logger.getLogger(PersonalRecommender.class.getName()).log(Level.SEVERE, null, ex);
        }
        // Write the network to a file
        try {
            kbb.saveKBNetworkAs(kbFile);
        } catch (IOException e) {
            System.out.println("Error writing the knowledge file");
        }
    }
    
    public static void buildVB(String name, List<Video> videos){
        BayesianBuilder vbb = new BayesianBuilder(path, name, kbFile);
        for (Video video : videos) {
            vbb.createNode(video);
            List<Parent> parents = vbi.getAnnotations(video.getId());
            vbb.createArc(video, parents);
        }
        // Write the network to a file
        try {
            vbb.saveKBNetworkAs(vbFile);
        } catch (IOException e) {
            System.out.println("Error writing the video file");
        }
    }
    
    public static void buildKB(String name){
        List<Entity> entities = kbi.getAllEntities();
        buildKB(name, entities);
    }
    
    public static void buildVB(String name, int src) {
        // Load all videos and connect them to the KB
        List<Video> videos = vbi.getAllVideos(src);
        buildVB(name, videos);
    }
    
    public static void buildGroupVB(String name, int n) {
        BayesianReader vb = new BayesianReader(path, vbFile, 0);
        BayesianBuilder gbb = new BayesianBuilder(path, name);
        for(int i=1; i<=n; i++){
            gbb.cloneNetwork(vb.getNetwork(), i);
        }
        gbb.cloneLayer(vb.getNetwork(), "Video", n);
        // Write the network to a file
        try {
            gbb.saveKBNetworkAs("Group"+n+".xdsl");
        } catch (IOException e) {
            System.out.println("Error writing the knowledge file");
        }
    }

    public static List<Video> getVideosWithTopic(String topic) {
        return vbi.getVideosWithTopic(topic);
    }

    private static <K extends Comparable, V extends Comparable> List<Map.Entry<K, V>>
            sortByValues(Map<K, V> map) {

        List<Map.Entry<K, V>> entries = new LinkedList<>(map.entrySet());
        Collections.sort(entries, new Comparator<Map.Entry<K, V>>() {
            @Override
            public int compare(Entry<K, V> o1, Entry<K, V> o2) {
                return o2.getValue().compareTo(o1.getValue());
            }
        });
        return entries;
    }

    public static List<Entity> getTopNodes() {
        return kbi.getTopLevelNodes();
    }

    public static List<Video> getAllVideos() {
        return vbi.getAllVideos(0);
    }

    public static void setLike(int user, int cid, int video, boolean like) throws Exception {
        User u = umap.get(user);
        Video v = vbi.getVideo("Video" + video);
        if (like) {
            u.like(v);
        } else {
            u.dislike(v);
        }
        umap.put(user, u);
        ubi.setLike(user, cid, video, like);
    }

    public static void setInterest(int user, int cid, String eid, boolean like) throws Exception {
        User u = umap.get(user);
        Entity e = kbi.getEntity(eid);
        if (like) {
            u.expressInterest(e);
        } else {
            u.expressDislike(e);
        }
        umap.put(user, u);
        ubi.setInterest(user, cid, eid, like);
    }
    
    public static void forgetUser(int uid){
        User u = umap.get(uid);
        u.setLikeList(new ArrayList<Video>());
        u.setDislikeList(new ArrayList<Video>());
        u.setExplicitInterests(new ArrayList<Entity>());
        u.setExplicitDislikes(new ArrayList<Entity>());
        ubi.forgetUser(uid);
    }
    
    public static Map<Entity, Double> getAnnotations(int vid) throws Exception{
        List<Parent> anno = vbi.getAnnotations("Video"+vid);
        Map<Entity, Double> annos = new HashMap<>();
        for(Parent p: anno){
            annos.put(kbi.getEntity(p.getIdentifier()), p.getProbability());
        }
        return annos;
    }
    
    public static void updatePref(int uid, boolean t, int v_threshold, int e_threshold, 
            int nvid, int nint){
        ubi.updatePreference(uid, t, v_threshold, e_threshold, nvid, nint);
        User u = umap.get(uid);
        Preference p = u.getPreferences();
        p.setPrefs(t, v_threshold, e_threshold, nvid, nint);
        u.setPreferences(p);
    }

    public static void updateContext(int uid, int cid) {
        ubi.setActiveContext(uid, cid);
        umap.put(uid, ubi.loadUser(uid));
    }

    public static int addContext(int cid, String n) {
        return ubi.addContext(cid, n);
    }
}
